/* Copyright (c) 2022-2023, Arm Limited and Contributors. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
#include "Driver_USART.h"

#include "cmsis_os2.h"

#define PW_LOG_MODULE_NAME "test"

#include "pw_assert/check.h"
#include "pw_log/log.h"
#include "pw_sys_io/sys_io.h"
#include "pw_sys_io_cmsis_driver/backend.h"
#include "pw_unit_test/internal/framework.h"
#include "pw_unit_test/simple_printing_event_handler.h"

extern "C" ARM_DRIVER_USART *get_example_serial();

extern "C" {

static void call_before_top_level_ctors(void)
{
    // Initialise the kernel before static constructors get called as Pigweed's
    // facade tests statically declare things like semaphores.
    osKernelInitialize();
}

#if defined(__ARMCLIB_VERSION) && defined(__ARMCOMPILER_VERSION)
// Both Arm C lib versions, that is Microlib and Standardlib, call constructors
// of top level objects from  __rt_lib_init(), in __cpp_initialize__aeabi_().
//
// Wrap __cpp_initialize__aeabi_(), and call our code just before actually
// iterating over the init array.
//
// An alternative is to define _platform_post_stackheap_init() which is called
// before the whole __rt_lib_init(). However, this approach is specific to the
// Standardlib, and not available when Microlib is used.

void $Super$$__cpp_initialize__aeabi_(void);
void $Sub$$__cpp_initialize__aeabi_(void);

void $Sub$$__cpp_initialize__aeabi_(void)
{
    call_before_top_level_ctors();
    $Super$$__cpp_initialize__aeabi_();
}

#elif defined(_NEWLIB_VERSION) && defined(__GNUC__)
// Newlib C executes constructors of top level objects from __libc_init_array()
// directly from _start().
//
// Wrap __libc_init_array(), and call our code just before actually iterating
// over the init array.
//
// An alternative is to define software_init_hook() which is called a bit before
// _init() (aka __libc_init_array). This approach would not be aligned with Arm
// C lib code above.

void __real___libc_init_array(void);
void __wrap___libc_init_array(void);

void __wrap___libc_init_array(void)
{
    call_before_top_level_ctors();
    __real___libc_init_array();
}

#endif
} // extern "C"

static void handler_write_cb(const std::string_view &s, bool append_newline)
{
    if (append_newline) {
        pw::sys_io::WriteLine(s);
    } else {
        pw::sys_io::WriteBytes(pw::as_bytes(pw::span(s)));
    }
}

static void main_task(void *ignored)
{
    (void)ignored;

    PW_LOG_INFO("Starting Pigweed Tests");
    PW_LOG_INFO("\u2702 --------------------");

    pw::unit_test::SimplePrintingEventHandler handler(handler_write_cb);
    pw::unit_test::RegisterEventHandler(&handler);

    int res = RUN_ALL_TESTS();

    PW_LOG_INFO("\u2702 ------------------------");
    PW_LOG_INFO("Pigweed reported status %d", res);

    osThreadExit();
}

static void serial_setup(ARM_DRIVER_USART *serial)
{
    if ((serial->Initialize(NULL) != ARM_DRIVER_OK) || (serial->PowerControl(ARM_POWER_FULL) != ARM_DRIVER_OK)
        || (serial->Control(ARM_USART_MODE_ASYNCHRONOUS, 115200) != ARM_DRIVER_OK)) {
        return;
    }
    /* Some drivers have TX and RX enabled by default and lacks option to enable/disable them. */
    int ret = serial->Control(ARM_USART_CONTROL_TX, 1);
    if (ret != ARM_DRIVER_OK && ret != ARM_DRIVER_ERROR_UNSUPPORTED) {
        return;
    }
    ret = serial->Control(ARM_USART_CONTROL_RX, 1);
    if (ret != ARM_DRIVER_OK && ret != ARM_DRIVER_ERROR_UNSUPPORTED) {
        return;
    }
}

int main()
{
    ARM_DRIVER_USART *serial = get_example_serial();
    serial_setup(serial);

    pw_sys_io_init(serial);

    osThreadId_t thread = osThreadNew(main_task, nullptr, nullptr);
    PW_CHECK_NOTNULL(thread, "osThreadNew failed");

    osKernelState_t state = osKernelGetState();
    PW_CHECK_INT_EQ(state, osKernelReady, "Kernel is not ready");

    osKernelStart();
    PW_LOG_ERROR("osKernelStart returned");
    while (true)
        ;
}
